using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using OpenTK;
using System.Drawing;
using System.Security.Cryptography;

namespace MKDS_Course_Editor
{
    class Obj2Nsbmd
    {
        public void Convert(string infile, string outfile)
        {
            LoadModel_OBJ(infile);
            EndianBinaryWriter er = new EndianBinaryWriter(File.Create(outfile), Endianness.LittleEndian);
            er.Write("BMD0", Encoding.ASCII, false);
            er.Write(new byte[] { 0xFF, 0xFE, 0x02, 0x00 }, 0, 4);
            er.Write((Int32)0);
            er.Write((Int16)0x10);
            er.Write((Int16)1);
            for (int i = 0; i < 1; i++)
            {
                er.Write((Int32)0x14);
            }
            er.Write("MDL0", Encoding.ASCII, false);
            er.Write((Int32)0);
            //Nitro 3d
            er.Write((byte)0);
            er.Write((byte)1);
            er.Write((Int16)0x28);

            er.Write((Int16)0x8);
            er.Write((Int16)0x10);
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < 1; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)0x8);
            for (int i = 0; i < 1; i++)
            {
                er.Write((Int32)0x30);
            }
            string name = Path.GetFileNameWithoutExtension(infile);
            if (name.Length > 16)
            {
                name = name.Remove(16);
            }
            for (int i = 0; i < 1; i++)
            {
                er.Write(name, Encoding.ASCII, false);
                if (name.Length != 16)
                {
                    er.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            long offsetpos = er.BaseStream.Position;
            er.Write((Int32)0); //MDL0 Length - 0x44
            er.Write((Int32)0); //Bones Offset - 0x44
            er.Write((Int32)0); //Materials Offset - 0x44
            er.Write((Int32)0); //Polygon Start Offset - 0x44
            er.Write((Int32)0); //Polygon End Offset - 0x44 (MDL0 Length - 0x44)
            er.Write((byte)0);
            er.Write((byte)0);
            er.Write((byte)0);
            er.Write((byte)1);
            er.Write((byte)m_Materials.Count);
            er.Write((byte)m_Materials.Count);
            er.Write((byte)1);
            er.Write((byte)0);

            Vector3 m_Scale = new Vector3(1, 1, 1);
            Vector4[] scaledvtxs = new Vector4[m_Vertices.Count];
            for (int i = 0; i < m_Vertices.Count; i++)
                scaledvtxs[i] = new Vector4(m_Vertices[i].X * m_Scale.X, m_Vertices[i].Y * m_Scale.Y, m_Vertices[i].Z * m_Scale.Z, m_Vertices[i].W);

            int poly = 0;
            int tri = 0;
            int quat = 0;
            Vector3 Min = new Vector3();
            Vector3 Max = new Vector3();
            foreach (Vector4 v in m_Vertices)
            {
                float x = v.X;
                float y = v.Y;
                float z = v.Z;
                if (x < Min.X || Min.X == 0) { Min.X = x; }
                if (y < Min.Y || Min.Y == 0) { Min.Y = y; }
                if (z < Min.Z || Min.Z == 0) { Min.Z = z; }
                if (x > Max.X || Max.X == 0) { Max.X = x; }
                if (y > Max.Y || Max.Y == 0) { Max.Y = y; }
                if (z > Max.Z || Max.Z == 0) { Max.Z = z; }
            }
            foreach (MaterialDef m in m_Materials.Values)
            {
                foreach (FaceDef f in m.m_Faces)
                {
                    poly++;
                    switch (f.m_VtxIndices.Length)
                    {
                        case 1:
                            break;
                        case 2:
                            break;
                        case 3:
                            {
                                //Vector4 a = m_Vertices[f.m_VtxIndices[0]];
                                //Vector4 b = m_Vertices[f.m_VtxIndices[1]];
                                //Vector4 c = m_Vertices[f.m_VtxIndices[2]];
                                tri++;
                                break;
                            }
                        case 4:
                            {
                                //Vector4 a = m_Vertices[f.m_VtxIndices[0]];
                                //Vector4 b = m_Vertices[f.m_VtxIndices[1]];
                                //Vector4 c = m_Vertices[f.m_VtxIndices[2]];
                                //Vector4 d = m_Vertices[f.m_VtxIndices[3]];
                                quat++;
                                break;
                            }
                    }
                }
            }
            float scale = 1f; uint scaleval = 0;
            float largest = new float[] { Max.X, Max.Y, Max.Z }.Max();
            while (largest > (32767f / 4096f))
            {
                scaleval++;
                scale /= 2f;
                largest /= 2f;
            }

            if (scaleval > 31)
            {
                System.Windows.Forms.MessageBox.Show("Your model is too large to be imported. Try scaling it down.");
                return;
            }
            for (int i = 0; i < scaledvtxs.Length; i++)
            {
                scaledvtxs[i].X *= scale;
                scaledvtxs[i].Y *= scale;
                scaledvtxs[i].Z *= scale;
            }
            er.Write((Int32)(4096));
            er.Write((Int32)4096);

            er.Write((Int16)m_Vertices.Count);
            er.Write((Int16)poly);
            er.Write((Int16)tri);
            er.Write((Int16)quat);

            er.Write((Int16)(Min.X * 4096));
            er.Write((Int16)(Min.Y * 4096));
            er.Write((Int16)(Min.Z * 4096));

            Vector3 hw = Max - Min;
            er.Write((Int16)(hw.X * 4096));
            er.Write((Int16)(hw.Y * 4096));
            er.Write((Int16)(hw.Z * 4096));

            er.Write((Int32)(4096));
            er.Write((Int32)(4096));

            //Bones
            er.Write((byte)0);
            er.Write((byte)1);
            er.Write((Int16)0x28);

            er.Write((Int16)0x8);
            er.Write((Int16)0x10);
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < 1; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)0x8);
            for (int i = 0; i < 1; i++)
            {
                er.Write((Int32)0x28);
            }

            er.Write("Root", Encoding.ASCII, false);
            er.Write(new byte[12], 0, 12);

            er.Write((byte)7);
            er.Write((byte)0);
            er.Write((byte)0);
            er.Write((byte)0);

            long curpos = er.BaseStream.Position;
            er.BaseStream.Position = offsetpos + 4;
            er.Write((Int32)curpos - 0x44);
            er.BaseStream.Position = curpos;

            er.Write((byte)0x26);//NodeDesc[001], 
            er.Write((Int32)0);  //0(Root), 0(Root), 0x00, StoreStackID=0
            er.Write((byte)0x2); //Node,
            er.Write((byte)0);   //0(Root),
            er.Write((byte)1);   //visible
            er.Write((byte)0x0b);//PosScale[000]
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((byte)0x04);//Mat[000]
                er.Write((byte)i);
                er.Write((byte)0x05);//Shp
                er.Write((byte)i);
            }
            er.Write((byte)0x2b);//PosScale[001]
            er.Write((byte)0x01);//Ret
            er.Write((byte)0x00);//Nop

            curpos = er.BaseStream.Position;
            er.BaseStream.Position = offsetpos + 8;
            er.Write((Int32)curpos - 0x44);
            er.BaseStream.Position = curpos;

            //Materials
            int mm = m_Materials.Count + m_Materials.Count;
            while (mm % 4 != 0) { mm++; }
            mm -= m_Materials.Count;
            mm -= m_Materials.Count;

            er.Write((Int16)((16 + 0x18 * m_Materials.Count) + 4)); //Tex
            er.Write((Int16)((16 + 0x18 * m_Materials.Count) * 2 + 4)); //Pal
            er.Write((byte)0);
            er.Write((byte)m_Materials.Count);
            er.Write((Int16)(16 + 0x18 * m_Materials.Count));

            er.Write((Int16)0x8);
            er.Write((Int16)(0x8 + m_Materials.Count * 4 + 4));
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)(0x4 + 0x4 * m_Materials.Count));
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int32)(((16 + 0x18 * m_Materials.Count) * 3 + 44 * i + 4) + m_Materials.Count + m_Materials.Count + mm));
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                name = m_Materials.Keys.ToArray()[i];
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                er.Write(name, Encoding.ASCII, false);
                if (name.Length != 16)
                {
                    er.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }

            er.Write((byte)0);
            er.Write((byte)m_Materials.Count);
            er.Write((Int16)(16 + 0x18 * m_Materials.Count));

            er.Write((Int16)0x8);
            er.Write((Int16)(0x8 + m_Materials.Count * 4 + 4));
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)(0x4 + 0x4 * m_Materials.Count));
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)((16 + 0x18 * m_Materials.Count) * 3 + m_Materials.Count + i + 4));
                er.Write((Int16)1);
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                name = m_Materials.Keys.ToArray()[i];
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                er.Write(name, Encoding.ASCII, false);
                if (name.Length != 16)
                {
                    er.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }

            er.Write((byte)0);
            er.Write((byte)m_Materials.Count);
            er.Write((Int16)(16 + 0x18 * m_Materials.Count));

            er.Write((Int16)0x8);
            er.Write((Int16)(0x8 + m_Materials.Count * 4 + 4));
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)(0x4 + 0x4 * m_Materials.Count));
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)((16 + 0x18 * m_Materials.Count) * 3 + i + 4));
                er.Write((Int16)1);
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                name = m_Materials.Keys.ToArray()[i] + "_pl";
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                er.Write(name, Encoding.ASCII, false);
                if (name.Length != 16)
                {
                    er.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((byte)i);
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((byte)i);
            }
            while (er.BaseStream.Position % 4 != 0) { er.Write((Byte)0); }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                uint alpha = (uint)(m_Materials.Values.ToArray()[i].m_Opacity >> 3);
                uint polyattr = 0;//0x00000080 | (alpha << 16);
                polyattr |= 3 << 6;
                polyattr |= 1 << 11;
                polyattr |= alpha << 16;
                er.Write((Int16)(0));
                er.Write((Int16)(44));
                er.Write((UInt16)(SM64DSe.Helper.ColorToBGR15(m_Materials.Values.ToArray()[i].m_DiffuseColor)));
                er.Write((UInt16)0);
                er.Write((UInt16)0);
                er.Write((UInt16)0);

                er.Write((UInt32)polyattr);
                er.Write(new byte[] { 0xff, 0xf8, 0x1f, 0x3f }, 0, 4);
                er.Write((UInt32)0x00030000);
                er.Write((UInt32)0xffffffff);
                er.Write((UInt32)0);
                er.Write((UInt16)0);//1);
                er.Write((UInt16)0);//1);
                er.Write((UInt32)0);//4096);
                er.Write((UInt32)0);//4096);
            }

            curpos = er.BaseStream.Position;
            er.BaseStream.Position = offsetpos + 12;
            er.Write((Int32)curpos - 0x44);
            er.BaseStream.Position = curpos;
            //Poly
            List<byte[]> dl = new List<byte[]>();
            SM64DSe.ModelImporter.GXDisplayListPacker dlpacker = new SM64DSe.ModelImporter.GXDisplayListPacker();
            int b = 0;
            foreach (MaterialDef mat in m_Materials.Values)
            {
                Vector2 tcscale = mat.m_DiffuseMapSize;

                float largesttc = 0f;
                foreach (FaceDef face in mat.m_Faces)
                {
                    foreach (int txci in face.m_TxcIndices)
                    {
                        if (txci < 0) continue;
                        Vector2 txc = Vector2.Multiply(m_TexCoords[txci], tcscale);
                        if (Math.Abs(txc.X) > largesttc) largesttc = Math.Abs(txc.X);
                        if (Math.Abs(txc.Y) > largesttc) largesttc = Math.Abs(txc.Y);
                    }
                }
                float _tcscale = largesttc / (32767f / 16f);
                if (_tcscale > 1f)
                {
                    _tcscale = (float)Math.Ceiling(_tcscale * 4096f) / 4096f;
                    mat.m_TexCoordScale = _tcscale;
                    Vector2.Divide(ref tcscale, _tcscale, out tcscale);
                }
                else
                    mat.m_TexCoordScale = 0f;

                dlpacker.ClearCommands();
                int lastface = -1;
                Vector4 lastvtx = new Vector4(0f, 0f, 0f, 12345678f);
                foreach (FaceDef face in mat.m_Faces)
                {
                    int nvtx = face.m_VtxIndices.Length;

                    if (nvtx != lastface || lastface > 4)
                    {
                        uint vtxtype = 0;
                        switch (nvtx)
                        {
                            case 1:
                            case 2:
                            case 3: vtxtype = 0; break;
                            case 4: vtxtype = 1; break;
                            default: vtxtype = 2; break;
                        }

                        if (lastface != -1) dlpacker.AddCommand(0x41);
                        dlpacker.AddCommand(0x40, vtxtype);
                        if (lastface == -1) dlpacker.AddCommand(0x14, 0);

                        lastface = nvtx;
                    }

                    dlpacker.AddColorCommand(mat.m_DiffuseColor);

                    switch (nvtx)
                    {
                        case 1: // point
                            {
                                Vector4 vtx = scaledvtxs[face.m_VtxIndices[0]];
                                int txc = face.m_TxcIndices[0];

                                if (txc > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc], tcscale));
                                dlpacker.AddVertexCommand(vtx, lastvtx);
                                if (txc > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc], tcscale));
                                dlpacker.AddVertexCommand(vtx, vtx);
                                if (txc > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc], tcscale));
                                dlpacker.AddVertexCommand(vtx, vtx);
                                lastvtx = vtx;
                            }
                            break;

                        case 2: // line
                            {
                                Vector4 vtx1 = scaledvtxs[face.m_VtxIndices[0]];
                                int txc1 = face.m_TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.m_VtxIndices[1]];
                                int txc2 = face.m_TxcIndices[1];

                                if (txc1 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc1], tcscale));
                                dlpacker.AddVertexCommand(vtx1, lastvtx);
                                if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                dlpacker.AddVertexCommand(vtx2, vtx1);
                                if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                dlpacker.AddVertexCommand(vtx2, vtx2);
                                lastvtx = vtx2;
                            }
                            break;

                        case 3: // triangle
                            {
                                Vector4 vtx1 = scaledvtxs[face.m_VtxIndices[0]];
                                int txc1 = face.m_TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.m_VtxIndices[1]];
                                int txc2 = face.m_TxcIndices[1];
                                Vector4 vtx3 = scaledvtxs[face.m_VtxIndices[2]];
                                int txc3 = face.m_TxcIndices[2];

                                if (txc1 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc1], tcscale));
                                dlpacker.AddVertexCommand(vtx1, lastvtx);
                                //if (m_ZMirror)
                                //{
                                //    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                //     dlpacker.AddVertexCommand(vtx3, vtx1);
                                //    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                //    dlpacker.AddVertexCommand(vtx2, vtx3);
                                //    lastvtx = vtx2;
                                //}
                                //else
                                {
                                    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpacker.AddVertexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpacker.AddVertexCommand(vtx3, vtx2);
                                    lastvtx = vtx3;
                                }
                            }
                            break;

                        case 4: // quad
                            {
                                Vector4 vtx1 = scaledvtxs[face.m_VtxIndices[0]];
                                int txc1 = face.m_TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.m_VtxIndices[1]];
                                int txc2 = face.m_TxcIndices[1];
                                Vector4 vtx3 = scaledvtxs[face.m_VtxIndices[2]];
                                int txc3 = face.m_TxcIndices[2];
                                Vector4 vtx4 = scaledvtxs[face.m_VtxIndices[3]];
                                int txc4 = face.m_TxcIndices[3];

                                if (txc1 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc1], tcscale));
                                dlpacker.AddVertexCommand(vtx1, lastvtx);
                                //if (m_ZMirror)
                                //{
                                //    if (txc4 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                //    dlpacker.AddVertexCommand(vtx4, vtx1);
                                //    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                //    dlpacker.AddVertexCommand(vtx3, vtx4);
                                //    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                //   dlpacker.AddVertexCommand(vtx2, vtx3);
                                //   lastvtx = vtx2;
                                //}
                                //else
                                {
                                    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpacker.AddVertexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpacker.AddVertexCommand(vtx3, vtx2);
                                    if (txc4 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                    dlpacker.AddVertexCommand(vtx4, vtx3);
                                    lastvtx = vtx4;
                                }
                            }
                            break;

                        default: // whatever (import as triangle strip)
                            {
                                // todo
                                /*for (int i = 0; i < nvtx - 4; i++)
                                {
                                    Vector4 vtx1 = scaledvtxs[face.m_VtxIndices[i+0]];
                                    int txc1 = face.m_TxcIndices[i + 0];
                                    Vector4 vtx2 = scaledvtxs[face.m_VtxIndices[i + 1]];
                                    int txc2 = face.m_TxcIndices[i + 1];
                                    Vector4 vtx3 = scaledvtxs[face.m_VtxIndices[i + 2]];
                                    int txc3 = face.m_TxcIndices[i + 2];
                                    Vector4 vtx4 = scaledvtxs[face.m_VtxIndices[i + 3]];
                                    int txc4 = face.m_TxcIndices[i + 3];
                                    Vector4 vtx5 = scaledvtxs[face.m_VtxIndices[i + 4]];
                                    int txc5 = face.m_TxcIndices[i + 4];

                                    if (txc1 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc1], tcscale));
                                    dlpacker.AddVertexCommand(vtx1, lastvtx);
                                    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpacker.AddVertexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpacker.AddVertexCommand(vtx3, vtx2);

                                    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpacker.AddVertexCommand(vtx3, vtx3);
                                    if (txc2 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpacker.AddVertexCommand(vtx2, vtx3);
                                    if (txc4 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                    dlpacker.AddVertexCommand(vtx4, vtx2);

                                    if (txc3 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpacker.AddVertexCommand(vtx3, vtx4);
                                    if (txc4 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                    dlpacker.AddVertexCommand(vtx4, vtx3);
                                    if (txc5 > -1) dlpacker.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc5], tcscale));
                                    dlpacker.AddVertexCommand(vtx5, vtx4);


                                    lastvtx = vtx5;
                                }*/
                            }
                            break;
                    }
                }
                dlpacker.AddCommand(0x41);
                byte[] dlist = dlpacker.GetDisplayList();
                dl.Add(dlist);
                b++;
            }



            er.Write((byte)0);
            er.Write((byte)m_Materials.Count);
            er.Write((Int16)(16 + 0x18 * m_Materials.Count));

            er.Write((Int16)0x8);
            er.Write((Int16)(0x8 + m_Materials.Count * 4 + 4));
            er.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)0x46);
                er.Write((Int16)0x1);
            }

            er.Write((Int16)0x4);
            er.Write((Int16)(0x4 + 0x4 * m_Materials.Count));
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((UInt32)((16 + 0x18 * m_Materials.Count) + i * 16));
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                name = m_Materials.Keys.ToArray()[i] + "_ply";
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                er.Write(name, Encoding.ASCII, false);
                if (name.Length != 16)
                {
                    er.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            int pos_ = m_Materials.Count * 16;
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write((Int16)0);
                er.Write((Int16)16);
                er.Write((Int16)0xD);
                er.Write((Int16)0);
                er.Write((Int32)pos_);
                er.Write((Int32)dl[i].Length);
                pos_ += dl[i].Length - 16;
            }
            for (int i = 0; i < m_Materials.Count; i++)
            {
                er.Write(dl[i], 0, dl[i].Length);
            }


            //Write sizes
            curpos = er.BaseStream.Position;
            er.BaseStream.Position = offsetpos;
            er.Write((Int32)curpos - 0x44);
            er.BaseStream.Position = offsetpos + 16;
            er.Write((Int32)curpos - 0x44);
            er.BaseStream.Position = 8;
            er.Write((Int32)curpos);
            er.BaseStream.Position = 0x18;
            er.Write((Int32)curpos - 0x14);
            er.Close();
        }
        private bool m_SketchupHack = false;
        private Dictionary<string, MaterialDef> m_Materials = new Dictionary<string, MaterialDef>();
        private Dictionary<string, MaterialDef> m_Textures = new Dictionary<string, MaterialDef>();
        private List<Vector4> m_Vertices = new List<Vector4>();
        private List<Vector2> m_TexCoords = new List<Vector2>();
        private List<Vector3> m_Normals = new List<Vector3>();
        class FaceDef
        {
            public int[] m_VtxIndices;
            public int[] m_TxcIndices;
            public int[] m_NrmIndices;
            public string m_MatName;
        }
        private MD5CryptoServiceProvider m_MD5 = new MD5CryptoServiceProvider();
        class MaterialDef
        {
            public List<FaceDef> m_Faces;

            public Color m_DiffuseColor;
            public int m_Opacity;

            public bool m_HasTextures;


            //public byte[] m_DiffuseMap;
            public string m_DiffuseMapName;
            public int m_DiffuseMapID;
            public Vector2 m_DiffuseMapSize;

            // haxx
            public float m_TexCoordScale;
        }
        private string HexString(byte[] crap)
        {
            string ret = "";
            foreach (byte b in crap)
                ret += b.ToString("X2");
            return ret;
        }
        private void OBJ_LoadMTL(string filename, string m_ModelPath)
        {
            Stream fs = File.OpenRead(filename);
            StreamReader sr = new StreamReader(fs);

            string curmaterial = "";
            CultureInfo usahax = new CultureInfo("en-US");

            string curline;
            while ((curline = sr.ReadLine()) != null)
            {
                curline = curline.Trim();

                // skip empty lines and comments
                if (curline.Length < 1) continue;
                if (curline[0] == '#')
                {
                    if (curline.Contains("SketchUp"))
                        m_SketchupHack = true;

                    continue;
                }

                string[] parts = curline.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 1) continue;

                switch (parts[0])
                {
                    case "newmtl": // new material definition
                        {
                            if (parts.Length < 2) continue;
                            curmaterial = parts[1];

                            MaterialDef mat = new MaterialDef();
                            mat.m_Faces = new List<FaceDef>();
                            mat.m_DiffuseColor = Color.White;
                            mat.m_Opacity = 255;
                            mat.m_HasTextures = false;
                            mat.m_DiffuseMapName = "";
                            mat.m_DiffuseMapID = 0;
                            mat.m_DiffuseMapSize = new Vector2(0f, 0f);
                            m_Materials.Add(curmaterial, mat);
                        }
                        break;

                    case "d":
                    case "Tr": // opacity
                        {
                            if (parts.Length < 2) continue;
                            float o = float.Parse(parts[1], usahax);
                            if (m_SketchupHack)
                                o *= 255;

                            MaterialDef mat = (MaterialDef)m_Materials[curmaterial];
                            mat.m_Opacity = Math.Max(0, Math.Min(255, (int)(o * 255)));
                        }
                        break;

                    case "Kd": // diffuse color
                        {
                            if (parts.Length < 4) continue;
                            float r = float.Parse(parts[1], usahax);
                            float g = float.Parse(parts[2], usahax);
                            float b = float.Parse(parts[3], usahax);
                            Color col;
                            try
                            {
                                col = Color.FromArgb((int)(r * 255), (int)(g * 255), (int)(b * 255));
                            }
                            catch { col = Color.Black; }

                            MaterialDef mat = (MaterialDef)m_Materials[curmaterial];
                            mat.m_DiffuseColor = col;
                        }
                        break;

                    case "map_Kd":
                    case "mapKd": // diffuse map (texture)
                        {
                            string texname = curline.Substring(parts[0].Length + 1).Trim();
                            Bitmap tex = new Bitmap(m_ModelPath + texname);

                            int width = 8, height = 8;
                            while (width < tex.Width) width *= 2;
                            while (height < tex.Height) height *= 2;

                            // cheap resizing for textures whose dimensions aren't power-of-two
                            if ((width != tex.Width) || (height != tex.Height))
                            {
                                Bitmap newbmp = new Bitmap(width, height);
                                Graphics g = Graphics.FromImage(newbmp);
                                g.DrawImage(tex, new Rectangle(0, 0, width, height));
                                tex = newbmp;
                            }

                            MaterialDef mat = (MaterialDef)m_Materials[curmaterial];
                            mat.m_HasTextures = true;

                            byte[] map = new byte[tex.Width * tex.Height * 4];
                            for (int y = 0; y < tex.Height; y++)
                            {
                                for (int x = 0; x < tex.Width; x++)
                                {
                                    Color pixel = tex.GetPixel(x, y);
                                    int pos = ((y * tex.Width) + x) * 4;

                                    map[pos] = pixel.B;
                                    map[pos + 1] = pixel.G;
                                    map[pos + 2] = pixel.R;
                                    map[pos + 3] = pixel.A;
                                }
                            }
                            //System.Drawing.Imaging.BitmapData lol = tex.LockBits(new Rectangle(0, 0, tex.Width, tex.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                            //System.Runtime.InteropServices.Marshal.Copy(lol.Scan0, map, 0, tex.Width * tex.Height * 4);

                            string imghash = HexString(m_MD5.ComputeHash(map));
                            if (m_Textures.ContainsKey(imghash))
                            {
                                MaterialDef mat2 = m_Textures[imghash];
                                mat.m_DiffuseMapName = mat2.m_DiffuseMapName;
                                mat.m_DiffuseMapID = mat2.m_DiffuseMapID;
                                mat.m_DiffuseMapSize = mat2.m_DiffuseMapSize;
                                break;
                            }

                            mat.m_DiffuseMapName = texname;
                            m_Textures.Add(imghash, mat);

                            mat.m_DiffuseMapSize.X = tex.Width;
                            mat.m_DiffuseMapSize.Y = tex.Height;
                        }
                        break;
                }
            }

            sr.Close();
        }

        private void LoadModel_OBJ(string m_ModelFileName)
        {
            Stream fs = File.OpenRead(m_ModelFileName);
            StreamReader sr = new StreamReader(fs);

            string curmaterial = "";
            CultureInfo usahax = new CultureInfo("en-US");

            string curline;
            while ((curline = sr.ReadLine()) != null)
            {
                curline = curline.Trim();

                // skip empty lines and comments
                if (curline.Length < 1) continue;
                if (curline[0] == '#') continue;

                string[] parts = curline.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 1) continue;

                switch (parts[0])
                {
                    case "mtllib": // material lib file
                        {
                            string filename = curline.Substring(parts[0].Length + 1).Trim();
                            OBJ_LoadMTL(m_ModelFileName.Replace(Path.GetFileName(m_ModelFileName), "") + filename, m_ModelFileName.Replace(Path.GetFileName(m_ModelFileName), ""));
                        }
                        break;

                    case "usemtl": // material name
                        if (parts.Length < 2) continue;
                        curmaterial = parts[1];
                        break;

                    case "v": // vertex
                        {
                            if (parts.Length < 4) continue;
                            float x = float.Parse(parts[1], usahax);
                            float y = float.Parse(parts[2], usahax);
                            float z = float.Parse(parts[3], usahax);
                            float w = 1f; //(parts.Length < 5) ? 1f : float.Parse(parts[4], usahax);

                            m_Vertices.Add(new Vector4(x, y, z, w));
                        }
                        break;

                    case "vt": // texcoord
                        {
                            if (parts.Length < 2) continue;
                            float s = float.Parse(parts[1], usahax);
                            float t = (parts.Length < 3) ? 0f : float.Parse(parts[2], usahax);

                            m_TexCoords.Add(new Vector2(s, t));
                        }
                        break;

                    case "vn": // normal
                        {
                            if (parts.Length < 4) continue;
                            float x = float.Parse(parts[1], usahax);
                            float y = float.Parse(parts[2], usahax);
                            float z = float.Parse(parts[3], usahax);

                            Vector3 vec = new Vector3(x, y, z);
                            vec.Normalize();
                            m_Normals.Add(vec);
                        }
                        break;

                    case "f": // face
                        {
                            if (parts.Length < 4) continue;
                            int nvtx = parts.Length - 1;

                            MaterialDef mat = (MaterialDef)m_Materials[curmaterial];
                            FaceDef face = new FaceDef();
                            face.m_MatName = curmaterial;
                            face.m_VtxIndices = new int[nvtx];
                            face.m_TxcIndices = new int[nvtx];
                            face.m_NrmIndices = new int[nvtx];

                            for (int i = 0; i < nvtx; i++)
                            {
                                string vtx = parts[i + 1];
                                string[] idxs = vtx.Split(new char[] { '/' });

                                face.m_VtxIndices[i] = int.Parse(idxs[0]) - 1;
                                face.m_TxcIndices[i] = (mat.m_HasTextures && idxs.Length >= 2 && idxs[1].Length > 0)
                                    ? (int.Parse(idxs[1]) - 1) : -1;
                                face.m_NrmIndices[i] = (idxs.Length >= 3) ? (int.Parse(idxs[2]) - 1) : -1;
                            }

                            mat.m_Faces.Add(face);
                        }
                        break;
                }
            }

            sr.Close();
        }
    }
}
